# AbstractPolymorphicPersisterBuilder.java

    package org.codefilarete.stalactite.engine.configurer.polymorphism;

    import java.util.Map;
    import java.util.Set;

    import org.codefilarete.stalactite.dsl.PolymorphismPolicy;
    import org.codefilarete.stalactite.dsl.RelationalMappingConfiguration;
    import org.codefilarete.stalactite.dsl.subentity.SubEntityMappingConfiguration;
    import org.codefilarete.stalactite.engine.configurer.AbstractIdentification;
    import org.codefilarete.stalactite.engine.configurer.AbstractIdentification.SingleColumnIdentification;
    import org.codefilarete.stalactite.engine.configurer.NamingConfiguration;
    import org.codefilarete.stalactite.engine.configurer.builder.PersisterBuilderContext;
    import org.codefilarete.stalactite.engine.configurer.RelationConfigurer;
    import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
    import org.codefilarete.stalactite.sql.ConnectionConfiguration;
    import org.codefilarete.stalactite.sql.Dialect;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.statement.binder.ColumnBinderRegistry;

    /**
     * @author Guillaume Mary
     */
    abstract class AbstractPolymorphicPersisterBuilder<C, I, T extends Table<T>> implements PolymorphismBuilder<C, I, T> {

    	protected final PolymorphismPolicy<C> polymorphismPolicy;
    	protected final ConfiguredRelationalPersister<C, I> mainPersister;
    	protected final AbstractIdentification<C, I> identification;
    	protected final ColumnBinderRegistry columnBinderRegistry;

    	protected final NamingConfiguration namingConfiguration;
    	protected final PersisterBuilderContext persisterBuilderContext;

    	protected AbstractPolymorphicPersisterBuilder(PolymorphismPolicy<C> polymorphismPolicy,
    												  AbstractIdentification<C, I> identification,
    												  ConfiguredRelationalPersister<C, I> mainPersister,
    												  ColumnBinderRegistry columnBinderRegistry,
    												  NamingConfiguration namingConfiguration,
    												  PersisterBuilderContext persisterBuilderContext) {
    		this.polymorphismPolicy = polymorphismPolicy;
    		this.identification = identification;
    		this.mainPersister = mainPersister;
    		this.columnBinderRegistry = columnBinderRegistry;
    		this.namingConfiguration = namingConfiguration;
    		this.persisterBuilderContext = persisterBuilderContext;
    	}

    	/**
    	 * Adds relations to given persisters which are expected to be subclass's one. Relations are not only one-to-one and one-to-many ones but also
    	 * Polymorphism ones (subclass can also be polymorphic, resulting in a kind of recursive call to create a polymorphism tree)
    	 *
    	 * <strong>Given persister Map may be modified</strong> by this method by replacing persisters by new ones.
    	 *
    	 * @param persisterPerSubclass persisters that need relation
    	 * @param dialect the {@link Dialect} use for type binding
    	 * @param connectionConfiguration the connection configuration
    	 */
    	protected <D extends C> void registerSubEntitiesRelations(Map<Class<D>, ConfiguredRelationalPersister<D, I>> persisterPerSubclass,
    															  Dialect dialect,
    															  ConnectionConfiguration connectionConfiguration) {
    		// we surround our relation configuration with cycle detection (see registerRelationCascades(..) implementation), this may seem too wide and
    		// could be closer to registerRelationCascades(..) method call (which actually requires it) but as doing such we also cover the case of 2
    		// subconfigurations using same entity in their relation
    		persisterBuilderContext.runInContext(mainPersister, () -> {
    			for (SubEntityMappingConfiguration<D> subConfiguration : (Set<SubEntityMappingConfiguration<D>>) (Set) this.polymorphismPolicy.getSubClasses()) {
    				ConfiguredRelationalPersister<D, I> subEntityPersister = persisterPerSubclass.get(subConfiguration.getEntityType());

    				if (subConfiguration.getPolymorphismPolicy() != null) {
    					assertSubPolymorphismIsSupported(subConfiguration.getPolymorphismPolicy());
    					subEntityPersister = buildSubPolymorphicPersister(subEntityPersister, subConfiguration.getPolymorphismPolicy(), dialect, connectionConfiguration);
    					persisterPerSubclass.put(subConfiguration.getEntityType(), subEntityPersister);
    				}

    				// We register relation of subclass persister to take into account its specific one-to-ones, one-to-manys and element collection mapping
    				this.registerRelationCascades(
    						subConfiguration,
    						dialect,
    						connectionConfiguration,
    						subEntityPersister);
    			}
    		});
    	}

    	abstract void assertSubPolymorphismIsSupported(PolymorphismPolicy<? extends C> subPolymorphismPolicy);

    	/**
    	 * Creates a polymorphic persister for an already-polymorphic case (this class) : used when main persister subclasses are also polymorphic.
    	 *
    	 * @param subPersister a subclass persister of our main persister
    	 * @param subPolymorphismPolicy the sub persister {@link PolymorphismPolicy}
    	 * @param dialect the {@link Dialect} use for type binding
    	 * @param connectionConfiguration the connection configuration
    	 */
    	private <D extends C> ConfiguredRelationalPersister<D, I> buildSubPolymorphicPersister(ConfiguredRelationalPersister<D, I> subPersister,
    																						   PolymorphismPolicy<D> subPolymorphismPolicy,
    																						   Dialect dialect,
    																						   ConnectionConfiguration connectionConfiguration) {
    		// we only have to call a polymorphic builder with given methods arguments, and same configuration values as this instance
    		PolymorphismPersisterBuilder<D, I, T> polymorphismPersisterBuilder = new PolymorphismPersisterBuilder<>(
    				subPolymorphismPolicy,
    				(SingleColumnIdentification<D, I>) identification,
    				subPersister,
    				columnBinderRegistry,
    				(Map) subPersister.getMapping().getPropertyToColumn(),
    				(Map) subPersister.getMapping().getReadonlyPropertyToColumn(),
    				subPersister.getMapping().getReadConverters(),
    				subPersister.getMapping().getWriteConverters(),
    				namingConfiguration,
    				persisterBuilderContext);
    		return polymorphismPersisterBuilder.build(dialect, connectionConfiguration);
    	}

    	private <D extends C> void registerRelationCascades(RelationalMappingConfiguration<D> entityMappingConfiguration,
    														Dialect dialect,
    														ConnectionConfiguration connectionConfiguration,
    														ConfiguredRelationalPersister<D, I> subEntityPersister) {
    		// Note that for now polymorphism configuration doesn't support many-to-many nor Map relation
    		RelationConfigurer<D, I> relationConfigurer = new RelationConfigurer<>(dialect,
    				connectionConfiguration,
    				subEntityPersister,
    				namingConfiguration,
    				persisterBuilderContext);
    		relationConfigurer.configureRelations(entityMappingConfiguration);
    	}
    }